VARIABLES
n border and background color register
v volume register
u number of columns
w tree character (49)
o top play-area limit position
z bottom play-area limit position
t offset to access corresponding screen color ram
p player's position
l number of collected hearts on the current level
j score, i.e., total number of collected hearts
f (random) position of the next screen element (tree, skull, heart)
h player's character (50,51,52,53)
b skull's character (54)
q new (candidate) player's position
x energy used
e vertical vs horizontal movement
c -1 for left/up movement,  +1 for right/down movement 


EXTENDED CODE (using CBM Prg Studio notation for special characters)
0poke56,28:poke52,28:n=36879:w=49:readb$:fork=1to48:poke7559+k,asc(mid$(b$,k)):poke7421+k,.:next
1v=n-1:pokev-9,255:g=198:u=22:z=8186:t=30720:h=52:poken,8:print"{clear}{reverse on}{green}ijkl":waitg,1:poke650,128
2l=.:x=5:y=44:m=9:o=7723:print"{clear}":p=7933:fora=.to9:ford=.to9+j/2:gosub9:pokef+t,5:pokef,w:next
3b=54:pokef,b:pokef+t,1:gosub9:pokef+t,2:pokef,211:next:pokeg,.:waitg,1:forq=.to439:pokeo+q+t,.:next
4poken,8:print"{home}"spc(6)"{reverse on}{red}S{cyan}"j"  {red}{reverse off}4{cyan}{reverse on}"m-xand15:ifpeek(p)=211thenpoken,90:j=j+1:l=l+1:x=x+(x>1)
5pokep+t,2:pokep,h:geta$:s=asc(a$+"@"):ifl>mthenprintspc(7)"{purple}{reverse on}completed":fori=tton:next:poken,8:goto2
6e=sand1:c=s-75+e:ifc=sgn(c)thenx=x+.1:h=s-23:pokep,160:q=p+c*21*e+c:if(peek(q)<>w)goto8
7on-(x<mandp<zandp>oandpeek(p)<>b)goto4:poken,109:printspc(m)"{yellow}{reverse on}end":fori=tton:next:j=.:pokeg,.:goto1
8p=q:pokev,m:pokev,.:fora=-2to2:q=p+t+a:pokeq-y,1:pokeq-u,1:pokeq,1:pokeq+u,1:pokeq+y,1:next:goto7
9f=2+o+2*a*u+rnd(.)*20:return:data"{16}{56}{124}{56}{124}{16}{16}{16}{24}{60}{24}{102}{153}{24}{36}{102}{24}{44}{152}{110}{25}{24}{40}{40}{24}{36}{24}{102}{153}{24}{36}{102}{24}{52}{25}{118}{152}{24}{40}{40}{62}{127}{73}{127}{119}{62}{42}{42}"


EXTENDED, COMMENTED, INDENTED CODE

0

// Move BASIC code and variables top address
poke56,28:poke52,28:

// Initialization
n=36879:v=n-1:w=49

// Read graphics data from strings and copy them into ram. Set character 32 (space) to all 0
readb$:fork=1to48:poke7559+k,asc(mid$(b$,k)):poke7421+k,.:next


1

// Initialization
v=n-1:

// Map first 64 characters into ram and last 128 into ram as reversed 
pokev-9,255:

// Initialization
g=198:u=22:z=8186:t=30720:h=52

// Set border and background color to black
poken,8:

// Display instructions, wait for key-press, clear the screen
print"{clear}{reverse on}{green}ijkl":waitg,1:print"{clear}":


// Set keyboard auto-repeat
poke650,128:

2

// (Re-)initialize energy and number of collected hearts
l=.:x=5:y=44:

// Initialization 
m=9:o=7723:

// (Re-)clear screen
print"{clear}":

// (Re-)initialization of player's position
p=7933:


// We only fill 10 alternating rows with trees, hearts and skulls
fora=.to9:
    // Number of trees depends on j, i.e., on level
    ford=.to9+j/2

3
        // Compute new random tree position
        gosub9

        // Display random trees
        pokef+t,5:pokef,w:
    next:

3
    // Initialization
    b=54:
    // Display last tree as skill
    pokef,b:pokef+t,1:

    // Compute new random position for a heart
    gosub9:

    // Display heart
    pokef+t,2:pokef,211:
next:

// Wait key-press
pokeg,.:waitg,1:

// Hide the characters by setting the foreground color to black
forq=.to439:pokeo+q+t,.:next

4 -- MAIN GAME LOOP

    // (Re-)set screen to black
    poken,8:

    // Display number of hearts and energy left
    print"{home}"spc(6)"{reverse on}{red}S{cyan}"j"   {red}{reverse off}4{cyan}{reverse on}"m-xand15:

    // If player is on heart, then:
    // - produce flash (change of background and border color until re-set at the start of this line)
    // - increase score
    // - increase number of hearts collected on this level
    // - reduce energy used if not already zero
    ifpeek(p)=211thenpoken,90:j=j+1:l=l+1:x=x+(x>1)

    5

    // Display player's
    pokep+t,2:pokep,h:

    // Read keyboard
    geta$:s=asc(a$+"@"):

    // If number of hearts collected on the level is 10, then display completed, wait, reset screen to black and go to next level
    ifl>mthen
        printspc(7)"{purple}{reverse on}completed":
        fori=tton:next:
        poken,8:
        goto2

    6
    // IJKL trick (see [*])
    e=sand1:c=s-75+e:ifc=sgn(c)thenx=x+.1:h=s-23:pokep,160:q=p+c*(21*e+1):

    // If new player's position is not a tree, the goto 8 (to set the player to the new position and show the surrounding area)
    if(peek(q)<>w)goto8

    7

// if neither energy left, nor reached top or botton limit, nor on a skull, then restart the main game loop (goto 4)
on-(x<mandp<zandp>oandpeek(p)<>b)
    goto4:

// Otherwise, change screen color, display end, pause and restart the game (goto 1)
poken,109:printspc(m)"{yellow}{reverse on}end":fori=tton:next:j=.:pokeg,.:goto1

8
// Change player's position, produce beep by bit-banging the volume register, display surrounding area by setting foreground color to white, goto 7
p=q:pokev,m:pokev,.:fora=-2to2:q=p+t+a:pokeq-y,1:pokeq-u,1:pokeq,1:pokeq+u,1:pokeq+y,1:next:goto7

9

// Compute random position
f=2+o+2*a*u+rnd(.)*20:return:

// 6 characters data ONLY using editable (possibly special) characters
data"{16}{56}{124}{56}{124}{16}{16}{16}{24}{60}{24}{102}{153}{24}{36}{102}{24}{44}{152}{110}{25}{24}{40}{40}{24}{36}{24}{102}{153}{24}{36}{102}{24}{52}{25}{118}{152}{24}{40}{40}{62}{127}{73}{127}{119}{62}{42}{42}"


[TRICK] [*]
I have come up with this trick (by myself) in 2019. It may be a new trick.
I do not use conditionals, nor precomputed offsets, nor Boolean expressions.
I use an interpolating formula that computes the offset from the ASCII code of the pressed key.

I exploit the special symmetry of the ASCII codes of the keys I J K L:
- I and K have odd codes and a distance of 2 bytes
- J and L have even codes and a distance of 2 bytes
- I and K have odd ASCII codes
- J and L have even ASCII codes
So, given s=ASC(a$) with a$ either I or J or K or L, we update the position p with
p=p+c*(21*e+1) 
where
- e=s and 1 -> parity of the code, i.e., vertical vs horizontal movement
- c=s-75+e -> -1 for left/up vs +1 for right/down
- 21*e+1 -> vertical vs horizontal offset absolute value, i.e., 1 vs 22 